Skip to content

Conversation

@AlexanderSchuetz97
Copy link
Contributor

@AlexanderSchuetz97 AlexanderSchuetz97 commented Nov 2, 2025

As discussed in #183

There are a few things I would like to mention.

  1. MSRV for this would be 1.81 due to error_in_core.
  2. I dont care about the function names. I have named them how I would name them. If you want a name changed then tell me and I shall do so swiftly.

Concerning the error_in_core MSRV bump, if that is unacceptable then we have 2 options.

  1. feature gate it. (add a msrv1_81 feature which is off by default)
  2. remove the error impl unless std feature is enable.

I don't care about the error impl personally so its your choice.

I have not tested this yet on my Windows computer and will only have access to one in about a Weeks time,
so yeah this is not quite yet ready to merge but It is at a state where you can take a look at it and tell me what you think. I will also get to fixing up the documentation once I have more time.

@AlexanderSchuetz97 AlexanderSchuetz97 marked this pull request as draft November 2, 2025 23:32
Copy link
Owner

@nagisa nagisa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an over-arching observation I would be quite interested in seeing what a breaking change from current AsRef<OsStr> to some in-crate trait (e.g. trait ToFilename) would look like. That way we'd be able to control the types available between non-std and std contexts without adding all these additional constructor functions.

where
P: AsRef<OsStr>,
{
//TODO Why is this an option?, we have Library::this?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's because in the OS-specific bindings I closely match the underlying OS API in case users wanted to use/had a use-case for e.g. dlopen(NULL, FLAGS) in this case. Library::this exists but it does not allow specifying arbitrary flags.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not intend to commit, this, my bad. I added a lot of comments to understand your code for myself and removed all those. I will remove this.

src/error.rs Outdated
Comment on lines 20 to 24
#[cfg(not(feature = "std"))]
pub struct WindowsError(pub(crate) i32);

#[cfg(feature = "std")]
pub struct WindowsError(pub(crate) std::io::Error);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we can unconditionally store just the error code and potentially explore replicating some of the niceties for the std::io::Error (such as the error description text via FormatMessageW) ourselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would break any software that relies on cause() to return io::Error.
I dont care about that, if you want it done this way then I shall do so.

@AlexanderSchuetz97
Copy link
Contributor Author

AlexanderSchuetz97 commented Nov 3, 2025

Concerning your idea of a trait to replace AsRef< OsStr > that would work, but it would likely be a full on breaking change.
Unless you intend to only use the trait on a method that doesnt exist. Reason is if someone has a newtype of OsString then he may have implemented AsRef for it. Unless I am mistaken the rust trait system does not permit us to do
impl LibLoadingFileNameTrait for T where T: AsRef< OsStr >

If you want me to do it with a trait then that is fine, but I would recommend a major version bump in this case.

@nagisa
Copy link
Owner

nagisa commented Nov 3, 2025

This crate is still pre-1.0 so breaking changes are still not a huge deal. As an example we also have #174 in the pipeline.

@AlexanderSchuetz97
Copy link
Contributor Author

AlexanderSchuetz97 commented Nov 3, 2025

I see, in that case I will try implement this with 2 traits. One called "ToFilename" and one called "ToSymbolName" that would make the mentioned pr obsolete.

Have you decided what we should do in regards to the msrv bump due to error in core?

Sidenote: I would prefer it if this pr could be merged in a reasonable timeframe after you are satisfied.

@nagisa
Copy link
Owner

nagisa commented Nov 3, 2025

Have you decided what we should do in regards to the msrv bump due to error in core?

We're making a breaking change anyway, so there is no reason to not bump the MSRV. Especially when we're doing so to a version that has been released a while ago. This is a good opportunity to proactively set MSRV to maybe 1.86 or 1.88 or something of the sort.

@AlexanderSchuetz97
Copy link
Contributor Author

In that case I will let you handle the msrv bump outside of this pr. I will bump it to the minimum required version. You can then bump it again to whatever you desire.

@AlexanderSchuetz97 AlexanderSchuetz97 marked this pull request as ready for review November 4, 2025 00:15
@AlexanderSchuetz97
Copy link
Contributor Author

AlexanderSchuetz97 commented Nov 4, 2025

@nagisa I am satisfied, please tell me what else must be changed for you to be able to merge this.
This change is unlikely to be that catastrophically breaking. Unless you were building libloading with no default features prior to this change then I cannot really think of a situation where this change would break anyting, naturally besides the MSRV Bump.

Copy link
Owner

@nagisa nagisa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking quite good so far! I appreciate you spending time to prototype this out.

src/lib.rs Outdated
Comment on lines 66 to 69
#[cfg(feature = "std")]
use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
#[cfg(feature = "std")]
use std::ffi::{OsStr, OsString};
Copy link
Owner

@nagisa nagisa Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: these uses could be moved to inside the function and/or references to them replaced by fully-qualified paths in order to avoid these getting accidental non-local use in future development more obviously.

(Of course the CI would catch such instances, but why defer when we can have rustc complain about it during cargo check.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ci didnt catch this, I will move it to the function anyways

/// - CString &CString &CStr
/// - OsString &OsString &OsStr
/// - PathBuf &PathBuf &Path
/// - &[u8] assumes utf8 data!
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On unixes neither a file path nor the symbol name are required to be a valid utf-8. They can often be arbitrary bytes which is why we have the OsString in libstd. IMO &[u8] should not require utf-8 encoding (as produced by str::as_bytes) on unix systems.

Documentation nit: the implementers of the trait already appear at the bottom of generated documentation. I don't think we should list the specific types, other than perhaps mentioning in passing that most common types you might want to use are supported. Specific implementations with their special requirements (such as use of &[u8] on windows platforms or those imposed by &[u16]) should probably document those requirements on the trait implementation itself.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For any utf-8 and utf-16 requirements I would also defer/link directly to the relevant methods on String or similar. Something along the lines of

/// The buffer must contain valid data for the purposes of [`String::from_utf16`].

or similar.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we drop the utf-8 requirement we should similarly drop the utf-16 requirement in my opinion. I have never seen a filename which cannot be described as utf-8. Sure you can construct such OsStrings but you can always express that same path with utf-8 to my knowledge. Alas I dont want to argue on this technicality that completely irrelevant for me. I will drop the utf-8 and utf-16 requirements and just check for zero byte element/ending and ensure that no interior 0 element exists.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Windows utf-16 (or wtf-8) are required for their *W suffixed APIs, though, and their *A style APIs are kinda a footgun. So it makes sense to me to have the utf-16 requirement for/on Windows.

I've seen non-utf8 paths on unixes in the past. Depends heavily on the locale used (which for east-asian countries still is often not utf-8.)

Speaking of &[u8] and &[u16] and cross-platform support: I'm not sure how we'd convert a &[u16] to &[u8] when &[u16] is not specified to be utf-16. At the same time the reverse is also unclear – how to convert arbitrary non-utf8 &[u8] to a Windows path? So I think these implementations need to be made available conditionally on the target (i.e. only have &[u8] for unix and &[u16] for windows), or we should omit them entirely and rely on OsStr(ing)/Path(Buf) for people to provide these weird paths (they can still use regular string in std-less environments.) Similarly to &[u8], CString is also unclear with regards to paths on Windows.

I'd say lets have implementations for Path{,Buf}, OsStr{,ing} (fully featured but std-only) and str{,ing} (utf-8 only but works without std) for now and we can add further options later as needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think u8 and u16 slices cannot be done cross platform. In my most recent local version that I made earlier I have as you mentioned just made the impl different.
On unix u16 must be utf-16, on windows not. On windows u8 must be utf8 on unix not. I will commit it later.

I think we should keep u8 and u16 slice around but comment that they behave differently on each platform for no std to be able to call this with arbitrary data. Otherwise no std would not be able to call it with a non str.

Alternative is implementing it for c_void ptr and just passing that in which pushes this complexit to the user. (But also all of the danger) I dont care either way.

I recommend you take a look at my latest version that I will commit later this evening and then we can decide what to do here. I think I resolved all your other pain points.

///
/// The data can be assumed to be formatted like the windows `LPCWSTR` if it is at all valid.
///
#[allow(unused)] //Posix doesnt use this
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think libloading being a semi-foundational library should avoid compiling dead code on platforms that don't use it. Should probably cfg-out the irrelevant methods. Could also explore having just one method that changes in signature based on the target (i.e. make the method names same.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considers this, but I didnt like 1 function taking a different FnOnce depending on os.
Sure we can cfg-if it out, thats preferable in my opinion.

/// - CString &CString &CStr
/// - OsString &OsString &OsStr
/// - PathBuf &PathBuf &Path
/// - &[u8] assumes utf8 data!
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should especially be not requiring utf-8, as symbols can be pretty much anything.

Similarly, I don't think it makes much sense to implement these traits for OsString or PathBuf. That's pretty weird type to want to use for a symbol name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ill remove the std section of the impl then. I was pretty tired when I copied that over and didnt think much about it. Yeah a path in a symbol name would be beyond weird your correct.

Ill also get rid of the utf-8/utf-16 requirement

Comment on lines 10 to 20
///
/// This function is guaranteed to error or invoke the `FnOnce` parameter,
/// and if called, return whatever the `FnOnce` returns.
///
/// The pointer parameter to the `FnOnce` is guaranteed to point to a valid 0 terminated
/// c-string.
///
/// The data the pointer points to is guaranteed to live until the `FnOnce` returns.
///
/// The data can be assumed to be 0 terminated utf-8 on unix or 0 terminated wtf-8 on windows.
///
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't really need some of these properties (utf-8/wtf-8) in order to look up a symbol name. Should only require a bare minimum that's required for the specific task. I think the requirement of not having interior null pointers (other than for the last byte) has also been lost.

I'm curious, though, is there any reason why you chose to use the closure approach here over retaining the signature from cstr_cow_from_bytes?

Copy link
Contributor Author

@AlexanderSchuetz97 AlexanderSchuetz97 Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The closure approach makes the lifetime guarantee of the pointer obvious and trivial to describe.
I saw many "odd" drop() that ensured lifetimes in the old impl. I didnt like that.

I will get rid of the last element in this doc block.


impl<T> AsSymbolName for T where T: private::AsSymbolNameSeal {}

impl private::AsSymbolNameSeal for &str {
Copy link
Owner

@nagisa nagisa Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that the only method of this trait takes a reference already, these traits should either be implemented for str, String (&String being superfluous, right?). We can then add an impl<S: AsSymbolName> AsSymbolName for &S to cover all the nested references. Similar to AsRef.

Or the method should be made to take self by value and the trait should continue being implemented for all the types it currently is for. Where the owned value is available (String, OsString, etc.) we can then make the implementation a bit more optimal by pushing a 0 byte to the end of the string without reallocating it whole in cstr_cow_from_bytes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your correct, I will change it so that it takes impl AsSymbol name just like it is for the filename. This was a oversight.

Copy link
Contributor Author

@AlexanderSchuetz97 AlexanderSchuetz97 Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit: Where did you see that I take a AsRef of the trait?

pub unsafe fn get<T>(&self, symbol: impl AsSymbolName) -> Result<Symbol<'_, T>, Error> { ... }

This is how usage currently looks, this is not AsRef?
Edit2: I now understand what you said. I will fix it.

@AlexanderSchuetz97
Copy link
Contributor Author

AlexanderSchuetz97 commented Nov 4, 2025

alright latest changes are using self instead of &self in the trait.
adjusting some documentation the way you have asked for.
aswell as make the slice [u8/u16] implementation be platform dependant.
The trait also no longer has dead code.
utf8-utf16 requirement is dropped as much as possible. (Only u8 and u16 on some platforms use it because theres nothing else that can be done)
I also removed all OsString variants from the symbol name trait as discussed they make no sense.
Also I adjusted the imports in lib.rs to no longer have multiple cfg-if's on the use statements.

I was also able to more or less unify the Cow logic for both u8 and u16 where we check for the 0 element
as well as check and append the trailing 0 byte.

Is there anything else you would like adjusted?

@nagisa
Copy link
Owner

nagisa commented Nov 5, 2025

Merged in 4b98285

@nagisa nagisa closed this Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants